import logging
from insight_agent.mcp_server_graph.src.mcp_server_graph.utils.neo4j_util import Neo4jDriver
from neo4j.time import DateTime


class GraphService:

    def get_user_car_family(self, users):
        """
        人车家场景查询图谱
        :param users:
        :return:
        """
        subgraph_cypher = (
            f"WITH {users} as users  "
            "UNWIND users as user  "
            "MATCH (u:User{user_id: user})-[r1:visited]-(a:Addr)  "
            "WHERE  a.add_type = '4S店'  "
            "WITH u, a, collect(r1) AS visits, count(r1) AS visitCnt  "
            # "WHERE visitCnt > 1  "    # 必须访问两次及以上4S店
            "MATCH path=(app:APP {app_name: '懂车帝'})<-[r3:use_app]-(p:Phone{current_operator: '移动'})-[r2:has]-(u)-[r1:visited]-(a)  "
            "OPTIONAL MATCH family = (e:User|Addr|LAC)-[r*0..2]->(e2) WHERE e.group_fam_id = u.group_fam_id AND e2.group_fam_id = u.group_fam_id  "
            "WITH u, count{(:User{group_fam_id: u.group_fam_id})} as familyCnt, path, collect(family) AS familyPaths  "
            "WITH u, familyCnt,   "
            "     [rel IN relationships(path) | elementId(rel)] AS reasonPath,  "
            "     nodes(path) AS pathNodes,  "
            "     relationships(path) AS pathRels,  "
            "     familyPaths  "
            "WITH u, familyCnt, reasonPath, pathNodes, pathRels, familyPaths,  "
            "     pathNodes + REDUCE(allNodes = [], fam IN familyPaths | allNodes + nodes(fam)) AS allNodes  "
            "WITH u, familyCnt, reasonPath, pathRels, allNodes, familyPaths,  "
            "     pathRels + REDUCE(allRels = [], fam IN familyPaths | allRels + relationships(fam)) AS allRels    "
            "WITH u, familyCnt, reasonPath,  "
            "     apoc.coll.toSet(allNodes) AS uniqueNodes,  "
            "     apoc.coll.toSet(allRels) AS uniqueRelationships  "
            "RETURN   "
            "    {  "
            "      id: elementId(u),  "
            "      labels: labels(u),  "
            "      properties: properties(u)  "
            "    } as user,  "
            "  reasonPath as reasonPath,   "
            "  {  "
            "    nodes: [node IN uniqueNodes | {  "
            "      id: elementId(node),  "
            "      labels: labels(node),  "
            "      properties: properties(node)  "
            "    }],  "
            "    relationships: [rel IN uniqueRelationships | {  "
            "      id: elementId(rel),  "
            "      type: type(rel),  "
            "      startNode: elementId(startNode(rel)),  "
            "      endNode: elementId(endNode(rel)),  "
            "      properties: properties(rel)  "
            "    }]  "
            "  } as graphData,  "
            "  familyCnt  "

        )
        logging.info(f'人车家查询cypher: {subgraph_cypher}')
        print(f'人车家查询cypher: {subgraph_cypher}')
        records, summary, keys = self.neo4j_driver.execute(
            subgraph_cypher)
        subgraphs = list(record.data() for record in records)
        logging.info(f'人车家详细数据：    {subgraphs}\n')

        # 二度邻居图数据后处理, 组装为返回格式
        vertex_set = {}
        edge_set = {}
        extra_info = ""
        for subgraph in subgraphs:
            for node in subgraph["graphData"]["nodes"]:
                self._add_node_base(vertex_set, node)
                # 预留节点颜色的设置, 置空时候前端显示默认颜色
                # node["color"] = "#FFC0CB"

            for rel in subgraph["graphData"]["relationships"]:
                self._add_edge_base(edge_set, rel)

            # 增加高亮边
            user_id = subgraph["user"]['properties']['user_id']
            familyCnt = subgraph["familyCnt"]
            reasonPath = subgraph['reasonPath']
            for reasonEdge in reasonPath:
                edge_set[reasonEdge]['color'] = "#FF0000"  # 推理边大红色高亮
            extra_info += f"人车家推理:用户 {user_id} 多次访问4S店且使用懂车帝APP，家庭成员为{familyCnt}，建议进行车型推荐\n"
        vertex_set, edge_set = self._convert_ids(vertex_set, edge_set)
        return vertex_set, edge_set, subgraph_cypher, extra_info

    def get_new_customer_recommendation(self, users):
        subgraph_cypher = (
            f"WITH {users} as users  "
            "UNWIND users as user "
            "MATCH (u:User{user_id: user}) "
            "WITH u.group_geo_id as comid "
            "MATCH (n:LAC {group_geo_id: comid}) "
            "WITH count(n) AS lacCount, comid "
            "MATCH (u:User {group_geo_id: comid}) "
            "WITH lacCount, count(u) AS userCount, comid "
            "MATCH (p:Phone {group_geo_id: comid}) "
            "match (otherUser:User)-[:has]->(p) where p.current_operator <> '移动'  "
            "WITH lacCount,userCount,  "
            "{ "
            "  avg_monthly_fee: avg(p.monthly_fee_rmb), "
            "  avg_free_call_minutes: avg(p.free_call_minutes), "
            "  avg_data_allowance_gb: avg(p.data_allowance_gb), "
            "  group_avg_daily_traffic_mb: avg(p.avg_daily_traffic_mb), "
            "  group_avg_domestic_daily_call_min: avg(p.avg_domestic_daily_call_min)  "
            "} as profile, "
            "  comid,  "
            "  apoc.coll.toSet(apoc.coll.flatten(collect(otherUser))) AS otherUsers "
            "MATCH group = (e:User|LAC)-[gr*0..2]->(e2) WHERE e.group_geo_id = comid AND e2.group_geo_id = comid "
            "WITH comid, lacCount, userCount,  "
            "  profile, "
            "  otherUsers, "
            "     apoc.coll.toSet(apoc.coll.flatten(collect(nodes(group)))) AS allNodes, "
            "     apoc.coll.toSet(apoc.coll.flatten(collect(relationships(group)))) AS allRels "
            "MATCH (p:Phone)-[r:use_app]->(end:APP) "
            "WHERE end.app_name IS NOT NULL AND r.online_duration_minutes IS NOT NULL "
            "WITH comid, lacCount, userCount, profile, allNodes, allRels, otherUsers, "
            "     end.app_name AS appName,  "
            "     sum(r.online_duration_minutes) AS totalDuration, "
            "     count(r) AS connectionCount "
            "WITH comid, lacCount, userCount, profile, allNodes, allRels, otherUsers, "
            "     collect({ "
            "         appName: appName, "
            "         totalDuration: totalDuration, "
            "         connectionCount: connectionCount "
            "     }) AS appProfiles "
            "RETURN comid, "
            "       { "
            "         nodes: [node IN allNodes | { "
            "           id: elementId(node), "
            "           labels: labels(node), "
            "           properties: properties(node) "
            "         }], "
            "         relationships: [rel IN allRels | { "
            "           id: elementId(rel), "
            "           type: type(rel), "
            "           startNode: elementId(startNode(rel)), "
            "           endNode: elementId(endNode(rel)), "
            "           properties: properties(rel) "
            "         }] "
            "       } AS graphData, "
            "       apoc.map.merge(profile, {appUsage: appProfiles}) AS profile,  "
            "       [otherUser IN otherUsers | { "
            "         id: elementId(otherUser), "
            "         label: labels(otherUser), "
            "         properties: properties(otherUser) "
            "       }] AS otherUsers"
        )
        logging.info(f'异网查询cypher: {subgraph_cypher}')
        print(f'异网查询cypher: {subgraph_cypher}')
        records, summary, keys = self.neo4j_driver.execute(
            subgraph_cypher)
        subgraphs = list(record.data() for record in records)
        logging.info(f'异网详细数据：    {subgraphs}\n')

        # 二度邻居图数据后处理, 组装为返回格式
        vertex_set = {}
        edge_set = {}
        extra_info = ""
        for subgraph in subgraphs:
            for node in subgraph["graphData"]["nodes"]:
                self._add_node_base(vertex_set, node)
                # 预留节点颜色的设置, 置空时候前端显示默认颜色
                # node["color"] = "#FFC0CB"

            for rel in subgraph["graphData"]["relationships"]:
                self._add_edge_base(edge_set, rel)

            # 增加高亮点
            group_id = subgraph["comid"]
            group_profile = subgraph["profile"]
            other_users = subgraph['otherUsers']
            for other_user in other_users:
                vertex_set[other_user['id']]['color'] = "#FE9900"  # 橙色
            extra_info += f'地理圈 {group_id} 画像为 {group_profile} 其中异网用户包括 {[user["properties"]["user_id"] for user in other_users]} 建议根据地理圈情况进行拉新推荐'
        vertex_set, edge_set = self._convert_ids(vertex_set, edge_set)
        return vertex_set, edge_set, subgraph_cypher, extra_info

    def get_current_customer_recommendation(self, users):
        subgraph_cypher = (
            f"WITH {users} as users  "
            "UNWIND users as user "
            "MATCH (u:User{user_id: user}) "
            "WITH u.group_geo_id as comid "
            "MATCH (n:LAC {group_geo_id: comid}) "
            "WITH count(n) AS lacCount, comid "
            "MATCH (u:User {group_geo_id: comid}) "
            "WITH lacCount, count(u) AS userCount, comid "
            "MATCH (p:Phone {group_geo_id: comid}) "
            "WITH comid, lacCount, userCount, "
            "     avg(p.monthly_fee_rmb) AS avg_monthly_fee, "
            "     avg(p.free_call_minutes) AS avg_free_call_minutes, "
            "     avg(p.data_allowance_gb) AS avg_data_allowance_gb, "
            "     avg(p.avg_daily_traffic_mb) AS group_avg_daily_traffic_mb, "
            "     avg(p.avg_domestic_daily_call_min) AS group_avg_domestic_daily_call_min "
            "OPTIONAL match (lowUser:User {group_geo_id: comid})-[:has]->(p:Phone {group_geo_id: comid}) where ( "
            "  p.monthly_fee_rmb < avg_monthly_fee*0.2 or  "
            "  p.free_call_minutes < avg_free_call_minutes*0.2 or  "
            "  p.data_allowance_gb < avg_data_allowance_gb*0.2 or  "
            "  p.avg_daily_traffic_mb < group_avg_daily_traffic_mb*0.2 or  "
            "  p.avg_domestic_daily_call_min < group_avg_domestic_daily_call_min*0.2 "
            ") "
            "WITH { "
            "    comid: comid,  "
            "    lacCount: lacCount, "
            "    userCount: userCount, "
            "    avg_monthly_fee: avg_monthly_fee, "
            "    avg_free_call_minutes: avg_free_call_minutes, "
            "    avg_data_allowance_gb: avg_data_allowance_gb, "
            "    group_avg_daily_traffic_mb: group_avg_daily_traffic_mb, "
            "    group_avg_domestic_daily_call_min: group_avg_domestic_daily_call_min "
            "  } AS profile,  "
            "  apoc.coll.toSet(apoc.coll.flatten(collect(DISTINCT lowUser))) AS lowUsers "
            "MATCH group = (e:User|LAC)-[gr*0..2]->(e2) WHERE e.group_geo_id = profile['comid'] AND e2.group_geo_id = profile['comid'] "
            "WITH  "
            "  profile, "
            "  lowUsers, "
            "     apoc.coll.toSet(apoc.coll.flatten(collect(nodes(group)))) AS allNodes, "
            "     apoc.coll.toSet(apoc.coll.flatten(collect(relationships(group)))) AS allRels "
            "MATCH (p:Phone)-[r:use_app]->(end:APP) "
            "WHERE end.app_name IS NOT NULL AND r.online_duration_minutes IS NOT NULL "
            "WITH profile, allNodes, allRels, lowUsers, "
            "     end.app_name AS appName,  "
            "     sum(r.online_duration_minutes) AS totalDuration, "
            "     count(r) AS connectionCount "
            "WITH profile, allNodes, allRels, lowUsers, "
            "     collect({ "
            "         appName: appName, "
            "         totalDuration: totalDuration, "
            "         connectionCount: connectionCount "
            "     }) AS appProfiles "
            "RETURN  "
            "       { "
            "         nodes: [node IN allNodes | { "
            "           id: elementId(node), "
            "           labels: labels(node), "
            "           properties: properties(node) "
            "         }], "
            "         relationships: [rel IN allRels | { "
            "           id: elementId(rel), "
            "           type: type(rel), "
            "           startNode: elementId(startNode(rel)), "
            "           endNode: elementId(endNode(rel)), "
            "           properties: properties(rel) "
            "         }] "
            "       } AS graphData, "
            "       apoc.map.merge(profile, {appUsage: appProfiles}) AS profile,  "
            "       [lowUser IN lowUsers | { "
            "         id: elementId(lowUser), "
            "         label: labels(lowUser), "
            "         properties: properties(lowUser) "
            "       }] AS lowUsers, allNodes, allRels "
        )
        logging.info(f'群体运营查询cypher: {subgraph_cypher}')
        print(f'群体运营查询cypher: {subgraph_cypher}')
        records, summary, keys = self.neo4j_driver.execute(
            subgraph_cypher)
        subgraphs = list(record.data() for record in records)
        logging.info(f'群体运营详细数据：    {subgraphs}\n')

        # 二度邻居图数据后处理, 组装为返回格式
        vertex_set = {}
        edge_set = {}
        extra_info = ""
        for subgraph in subgraphs:
            for node in subgraph["graphData"]["nodes"]:
                self._add_node_base(vertex_set, node)
                # 预留节点颜色的设置, 置空时候前端显示默认颜色
                # node["color"] = "#FFC0CB"

            for rel in subgraph["graphData"]["relationships"]:
                self._add_edge_base(edge_set, rel)

            # 增加高亮点
            group_profile = subgraph["profile"]
            low_users = subgraph['lowUsers']
            for low_user in low_users:
                vertex_set[low_user['id']]['color'] = "#FE9900"  # 橙色
            extra_info += f'地理圈 {group_profile["comid"]} 画像为 {group_profile} 其中离群用户包括 {[user["properties"]["user_id"] for user in low_users]} 建议根据地理圈情况进行产品升级推荐'
        vertex_set, edge_set = self._convert_ids(vertex_set, edge_set)
        return vertex_set, edge_set, subgraph_cypher, extra_info

    def get_group_family(self, group_fam_id):
        """家庭圈查询"""
        subgraph_cypher = (
            f'WITH {group_fam_id} AS comid MATCH (e)-[r]-(e2) '
            'WHERE e.group_fam_id = comid AND e2.group_fam_id = comid '
            'RETURN *'
        )
        logging.info(f'家庭圈查询cypher: {subgraph_cypher}')
        print(f'家庭圈查询cypher: {subgraph_cypher}')
        records, summary, keys = self.neo4j_driver.execute(
            subgraph_cypher)
        subgraph = list(record.data() for record in records)
        logging.info(f'家庭圈详细数据：    {subgraph}\n')
        print(f'家庭圈详细数据：    {subgraph}\n')

    def get_fraud_meta(self, users):
        subgraph_cypher = (
            f"""
            //诈骗 meta
            //返回 用户点、子图、统计诈骗特征情况三元组
            with {users} as users
            MATCH (u:User)-[has:has]->(p:Phone)
            WHERE u.user_id in users 
            AND date(p.activation_date) >= date() - duration({{months: 12*9}})

            WITH u, has, p,
            count {{(p)-[]-(:Phone)}} AS callCnt,
            count {{(p)-[]->(:Phone)}} AS calltoCnt,
            count {{(p)<-[]-(:Phone)}} AS callfromCnt
            WHERE calltoCnt * 100 / callCnt > 70

            MATCH (p)-[short_forien_call:called]-(op:Phone)
            WHERE short_forien_call.call_duration_minutes < 10 
            AND short_forien_call.roaming_type = '国际'
            WITH u, has, p, 
            collect(short_forien_call) AS cs, 
            collect(op) AS ops, 
            callCnt,
            calltoCnt,
            callfromCnt,
            count(short_forien_call) AS shortForienCnt
            WHERE shortForienCnt * 100 / callCnt > 60

            // 构建子图结构
            WITH u, 
            [p] + ops AS allNodes,  // 所有节点：用户手机 + 关联手机
            [has] + cs AS allRels,  // 所有关系：拥有关系 + 通话关系
            callCnt,
            calltoCnt,
            callfromCnt,
            shortForienCnt

            // 返回用户及其子图
            RETURN {{
                id: elementId(u),
                labels: labels(u),
                properties: properties(u)
            }} AS user,
            {{
                nodes: apoc.coll.toSet([n IN allNodes | 
                {{
                    id: elementId(n),
                    labels: labels(n),
                    properties: properties(n)
                }}
                ]),
                relationships: apoc.coll.toSet([r IN allRels |
                {{
                    id: elementId(r),
                    type: type(r),
                    startNode: elementId(startNode(r)),
                    endNode: elementId(endNode(r)),
                    properties: properties(r)
                }}
                ])
            }} AS graphData,
                {{
                callCount: callCnt,
                shortForeignCallCount: shortForienCnt,
                callToPercentage: calltoCnt * 100 / callCnt,
                shortForeignCallPercentage: shortForienCnt * 100 / callCnt
                }} as stats limit 1
            """
        )
        logging.info(f'诈骗识别查询cypher: {subgraph_cypher}')
        print(f'诈骗识别查询cypher: {subgraph_cypher}')
        records, summary, keys = self.neo4j_driver.execute(subgraph_cypher)
        print(f'诈骗识别查询结果：    {records}\n')

        subgraphs = list(record.data() for record in records)
        logging.info(f'诈骗识别详细数据：    {subgraphs}\n')
        print(f'诈骗识别详细数据：    {subgraphs}\n')

        # 诈骗识别图数据后处理, 组装为返回格式
        vertex_set = {}
        edge_set = {}
        extra_info = ""
        for subgraph in subgraphs:
            user = subgraph["user"]
            for node in subgraph["graphData"]["nodes"]:
                self._add_node_base(vertex_set, node)

            # 诈骗用户节点标记为红色
            self._add_node_base(vertex_set, user)
            user["color"] = "#FF0000"  # 红色
            for rel in subgraph["graphData"]["relationships"]:
                self._add_edge_base(edge_set, rel)
                # 诈骗手机号标记为红色
                if rel["label"] == "has":
                    vertex_set[rel["dstId"]]["color"] = "#FF0000"  # 红色
        vertex_set, edge_set = self._convert_ids(vertex_set, edge_set)
        # 构建诈骗用户节点信息
        fraud_users = []
        for subgraph in subgraphs:
            user_node = subgraph["user"]
            stats = subgraph["stats"]
            fraud_users.append({
                "user_id": user_node["properties"].get("user_id", ""),
                "name": user_node["properties"].get("name", ""),
                "call_count": stats["callCount"],
                "short_foreign_call_count": stats["shortForeignCallCount"],
                "call_to_percentage": stats["callToPercentage"],
                "short_foreign_call_percentage": stats["shortForeignCallPercentage"]
            })

        extra_info = {
            "fraud_users": fraud_users,
            "total_fraud_users": len(fraud_users)
        }
        return vertex_set, edge_set, subgraph_cypher, extra_info

    def get_group_product_recommendation(self, users):
        subgraph_cypher = (
            # 查询用户列表中每个用户订购了产品，但是其工作的集团没有订购该产品，返回集团名称、产品名称、子图结构
            f'WITH {users} as users  '
            'UNWIND users as user  '
            'OPTIONAL MATCH path = (g:Group)<-[works_for:works_for]-(u:User {user_id: user})-['
            'user_use_p:use_product]->(p:Product)'
            'WHERE NOT (g)-[:use_product]->(p) '
            'WITH path, u, g, p '
            'WHERE path IS NOT NULL '
            'WITH  '
            '  collect(DISTINCT nodes(path)) AS allNodesList, '
            '  collect(DISTINCT relationships(path)) AS allRelsList, '
            '  u, g, p '
            'UNWIND allNodesList AS nodesList '
            'UNWIND nodesList AS node '
            'WITH collect(DISTINCT node) AS uniqueNodes, allRelsList, u, g, p '
            'UNWIND allRelsList AS relsList '
            'UNWIND relsList AS rel '
            'WITH uniqueNodes, collect(DISTINCT rel) AS uniqueRelationships, u, g, p '
            'RETURN  '
            '  { '
            '    id: elementId(u), '
            '    labels: labels(u), '
            '    properties: properties(u) '
            '  } as user, '
            '  { '
            '    startNode: { '
            '      id: elementId(g), '
            '      labels: labels(g), '
            '      properties: properties(g) '
            '    },  '
            '    endNode: { '
            '      id: elementId(p), '
            '      labels: labels(p), '
            '      properties: properties(p) '
            '    } '
            '  } as edgePrediction, '
            '  { '
            '    nodes: [n IN uniqueNodes | { '
            '      id: elementId(n), '
            '      labels: labels(n), '
            '      properties: properties(n) '
            '    }], '
            '    relationships: [r IN uniqueRelationships | { '
            '      id: elementId(r), '
            '      type: type(r), '
            '      startNode: elementId(startNode(r)), '
            '      endNode: elementId(endNode(r)), '
            '      properties: properties(r) '
            '    }] '
            '  }as graphData ')
        logging.info(f'用户细分查询cypher: {subgraph_cypher}')
        records, summary, keys = self.neo4j_driver.execute(
            subgraph_cypher)
        subgraphs = list(record.data() for record in records)
        logging.info(f'用户细分详细数据：    {subgraphs}\n')

        # 二度邻居图数据后处理, 组装为返回格式
        vertex_set = {}
        edge_set = {}
        extra_info = ""
        for subgraph in subgraphs:
            for node in subgraph["graphData"]["nodes"]:
                self._add_node_base(vertex_set, node)
                # 预留节点颜色的设置, 置空时候前端显示默认颜色
                # node["color"] = "#FFC0CB"

            for rel in subgraph["graphData"]["relationships"]:
                self._add_edge_base(edge_set, rel)

            # 增加推理边
            user_id = subgraph["user"]['properties']['user_id']
            group_name = subgraph["edgePrediction"]['startNode']['properties']['group_name']
            product_name = subgraph["edgePrediction"]['endNode']['properties']['product_name']
            info = f"产品推荐路径:用户 {user_id} 供职的公司 {group_name} 未使用 {product_name} 产品，建议进行推荐\n"
            extra_info += info
            edge_set[f'{user_id}-{group_name}-{product_name}'] = {
                "label": "prediction",
                "srcId": subgraph["edgePrediction"]['startNode']['id'],
                "dstId": subgraph["edgePrediction"]['endNode']['id'],
                "properties": {"description": info},
                "dashed": 1,
                "color": "#FF0000"  # 推理边大红色高亮
            }
        vertex_set, edge_set = self._convert_ids(vertex_set, edge_set)
        return vertex_set, edge_set, subgraph_cypher, extra_info

    def get_2_step_neighbour_meta(self, users):
        """二度邻居查询，带id和label"""
        subgraph_cypher = (
            # 查询二度邻居，避开打电话和基站关系聚合过多陌生人，同时过滤掉使用app不足100分钟的流量行为
            f'WITH {users} as users '
            'MATCH path = (u:User)-[r:lives_in|has|works_for|day_connect|night_connect|visited*1..2]->(end)  '
            'WHERE u.user_id IN users  '
            'OPTIONAL MATCH appPath = (u)-->(p:Phone)-[use_app:use_app]->(app:APP) '
            'WHERE use_app.online_duration_minutes > 100 '
            'WITH  '
            'collect(path) AS paths,  '
            'collect(appPath) AS appPaths, '
            'users '
            'UNWIND paths + appPaths AS allPaths '
            'UNWIND nodes(allPaths) AS node '
            'WITH DISTINCT node, users, paths, appPaths '
            'WHERE node IS NOT NULL '
            'WITH collect({ '
            'id: elementId(node), '
            'labels: labels(node), '
            'properties: properties(node) '
            '}) AS nodeData, users, paths, appPaths '
            'WITH nodeData, users, paths, appPaths '
            'UNWIND paths + appPaths AS allPaths '
            'UNWIND relationships(allPaths) AS rel '
            'WITH DISTINCT rel, nodeData '
            'WITH nodeData, collect({ '
            'id: elementId(rel), '
            'type: type(rel), '
            'startNode: elementId(startNode(rel)), '
            'endNode: elementId(endNode(rel)), '
            'properties: properties(rel) '
            '}) AS relationshipData '
            'RETURN { '
            'nodes: nodeData, '
            'relationships: relationshipData '
            '} AS graphData '
        )
        logging.info(f'二度邻居查询cypher: {subgraph_cypher}')
        # print(f'二度邻居查询cypher: {subgraph_cypher}')
        records, summary, keys = self.neo4j_driver.execute(
            subgraph_cypher)
        subgraphs = list(record.data() for record in records)
        logging.info(f'二度邻居详细数据：    {subgraphs}\n')
        # print(f'二度邻居详细数据：    {subgraph}\n')

        # 二度邻居图数据后处理, 组装为返回格式
        vertex_set = {}
        edge_set = {}
        for subgraph in subgraphs:
            for node in subgraph["graphData"]["nodes"]:
                self._add_node_base(vertex_set, node)
                # 预留节点颜色的设置, 置空时候前端显示默认颜色
                # node["color"] = "#FFC0CB"

            for rel in subgraph["graphData"]["relationships"]:
                self._add_edge_base(edge_set, rel)
        vertex_set, edge_set = self._convert_ids(vertex_set, edge_set)
        return vertex_set, edge_set, subgraph_cypher

    def simplify_for_explain(self, vertex_set, edge_set):
        """
        为大模型解释进一步简化图结构
        :param vertex_set: 
        :param edge_set: 
        :return: 
        """
        vertex_list = []
        edge_list = []
        for vertexId in vertex_set:
            vertex_for_explain = vertex_set[vertexId].copy()
            # del vertex_for_explain['id']
            vertex_list.append(vertex_for_explain)

        for edgeId in edge_set:
            edge_for_explain = edge_set[edgeId].copy()
            del edge_for_explain['id']
            edge_list.append(edge_for_explain)

        return vertex_list, edge_list

    def get_rand_users(self, limit=10):
        records, summary, keys = self.neo4j_driver.execute(
            "MATCH (n:User) "
            "WITH n, rand() AS r "
            "ORDER BY r "
            "RETURN n.user_id as user_id "
            f"LIMIT {limit} ")
        return list(record.data()['user_id'] for record in records)

    """ Helpers """

    def _convert_ids(self, vertex_set, edge_set):
        """
        转换点集、边集的内置id为业务主键，每种节点的主键名称记录在id_map中
        :param vertex_set:
        :param edge_set:
        :return:
        """
        id_map = {
            "User": "user_id",
            "Phone": "mobile_number",
            "LAC": "lac_code",
            "APP": "app_name",
            "Product": "product_name",
            "Group": "group_name",
            "Addr": "addr",
            "BrdAddr": "addr",
        }
        new_edge_set = {}
        for edge_id in edge_set:
            edge = edge_set[edge_id]
            start_id = edge['srcId']
            end_id = edge['dstId']
            if start_id not in vertex_set or end_id not in vertex_set:
                logging.warning(f"边 {edge_id} 引用了不存在的节点: src={start_id}, dst={end_id}")
                continue
            start_vertex = vertex_set[start_id]
            end_vertex = vertex_set[end_id]
            start_buz_id = start_vertex['properties'][id_map[start_vertex['label']]]
            end_buz_id = end_vertex['properties'][id_map[end_vertex['label']]]
            clean_start_buz_id = str(start_buz_id).translate(str.maketrans('', '', '@#$%^&*()_+'))
            clean_end_buz_id = str(end_buz_id).translate(str.maketrans('', '', '@#$%^&*()_+'))
            edge['srcId'] = clean_start_buz_id
            edge['dstId'] = clean_end_buz_id
            i = 0
            while f"{clean_start_buz_id}-{edge['label']}-{clean_end_buz_id}-{i}" in new_edge_set:
                i += 1
            edge_buz_id = f"{clean_start_buz_id}-{edge['label']}-{clean_end_buz_id}-{i}"
            edge['id'] = str(edge_buz_id)
            new_edge_set[str(edge_buz_id)] = edge

        new_vertex_set = {}
        for vertex_id in vertex_set:
            vertex = vertex_set[vertex_id]
            vertex_buz_id = vertex['properties'][id_map[vertex['label']]]
            clean_vertex_buz_id = str(vertex_buz_id).translate(str.maketrans('', '', '@#$%^&*()_+'))
            vertex['id'] = clean_vertex_buz_id
            vertex['label'] = f"{clean_vertex_buz_id}"
            new_vertex_set[clean_vertex_buz_id] = vertex

        return new_vertex_set, new_edge_set

    def _add_node_base(self, vertex_set, node):
        """
        处理基础节点信息并加到vertex_set
        :param vertex_set:
        :param node:
        :return:
        """
        vertex_set[node["id"]] = node
        node["label"] = node["labels"][0]
        del node["labels"]
        if "group_fam_id" in node["properties"]:
            node["properties"]["groupId"] = node["properties"]["group_fam_id"]
        elif "group_geo_id" in node["properties"]:
            node["properties"]["groupId"] = node["properties"]["group_geo_id"]
        self._convert_neo4j_date(node['properties'])  # 必做，neo4j时间对象不能序列化

    def _add_edge_base(self, edge_set, rel):
        """
        处理基础边信息并加到edge_set
        :param edge_set:
        :param rel:
        :return:
        """
        edge_set[rel["id"]] = rel
        rel["label"] = rel["type"]
        del rel["type"]
        rel["srcId"] = rel["startNode"]
        rel["dstId"] = rel["endNode"]
        del rel["startNode"]
        del rel["endNode"]
        if "group_fam_id" in rel["properties"]:
            rel["properties"]["groupId"] = rel["properties"]["group_fam_id"]
        elif "group_geo_id" in rel["properties"]:
            rel["properties"]["groupId"] = rel["properties"]["group_geo_id"]
        self._convert_neo4j_date(rel['properties'])  # 必做，neo4j时间对象不能序列化

    def _convert_neo4j_date(self, properties: dict):
        """转化一个dict中类型为neo4j.time.DateTime的值为string"""
        for k in properties:
            if isinstance(properties[k], DateTime):
                properties[k] = str(properties[k])[:20]
        if 'weight' in properties:
            del properties['weight']

    def __init__(self):
        logging.info("初始化GraphService")
        self.neo4j_driver = Neo4jDriver()


if __name__ == '__main__':
    from pathlib import Path
    from dotenv import load_dotenv

    # env_path = Path('/Users/lijia/PycharmProjects/user-insight/insight_agent') / '.env'
    # load_dotenv(dotenv_path=env_path)
    graph_service = GraphService()
    # print(graph_service.get_2_step_neighbour_meta(['U01118']))

    # print(extra_info1)
    # print(edge_set1)
    # print(subgraph)
    # vertex_set, edge_set, extra_info = graph_service.get_new_customer_recommendation(['U02369'])
    # vertex_set, edge_set = graph_service.get_2_step_neighbour_meta(['U02369'])
    # print("===========")
    # print(vertex_set)
    # print("===========")
    # print(edge_set)
    # print("===========")
    # print(extra_info)
    # print("===========")
    # graph_service.simplify_for_explain(vertex_set, edge_set)

    # print(vertex_set)
    # print("===========")
    # print(edge_set)
    # print("===========")

    print(graph_service.get_2_step_neighbour_meta(['U01118', 'U01117', 'U01137']))
    # print(graph_service.get_2_step_neighbour_meta(['U01118']))
